package com.ziroom.crm.mq.core.logging;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.LoggerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;

import java.io.Serializable;

/**
 * <p></p>
 * <p>
 * <PRE>
 * <BR>    修改记录
 * <BR>-----------------------------------------------
 * <BR>    修改日期         修改人          修改内容
 * </PRE>
 *
 * @author WeiShaoying
 * @version 1.0
 * @date Created in 2020年08月28日 14:48
 * @since 1.0
 */
public class LogFactory {
    private static LogApi logApi = LogApi.SLF4J;

    static {
        ClassLoader cl = LogFactory.class.getClassLoader();
        try {
            // Try Log4j 2.x API
            cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
            logApi = LogApi.LOG4J;
        }
        catch (ClassNotFoundException ex1) {
            try {
                // Try SLF4J 1.7 SPI
                cl.loadClass("org.slf4j.spi.LocationAwareLogger");
                logApi = LogApi.SLF4J_LAL;
            }
            catch (ClassNotFoundException ex2) {
                try {
                    // Try SLF4J 1.7 API
                    cl.loadClass("org.slf4j.Logger");
                    logApi = LogApi.SLF4J;
                }
                catch (ClassNotFoundException ex3) {
                    // Keep java.util.logging as default
                }
            }
        }
    }

    /**
     * Convenience method to return a named logger.
     * @param name logical name of the <code>Log</code> instance to be returned
     */
    public static Log getLog(String name) {
        switch (logApi) {
            case LOG4J:
                return Log4jDelegate.createLog(name);
            case SLF4J_LAL:
                return Slf4jDelegate.createLocationAwareLog(name);
            case SLF4J:
                return Slf4jDelegate.createLog(name);
            default:
                throw new RuntimeException("not found avoid valid logging api");
        }
    }

    /**
     * Convenience method to return a named logger.
     * @param clazz containing Class from which a log name will be derived
     */
    public static Log getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }

    private static class Log4jDelegate {

        public static Log createLog(String name) {
            return new Log4jLog(name);
        }
    }


    private static class Slf4jDelegate {

        public static Log createLocationAwareLog(String name) {
            Logger logger = LoggerFactory.getLogger(name);
            return (logger instanceof LocationAwareLogger ?
                    new Slf4jLocationAwareLog((LocationAwareLogger) logger) : new Slf4jLog<>(logger));
        }

        public static Log createLog(String name) {
            return new Slf4jLog<>(LoggerFactory.getLogger(name));
        }
    }


    private enum LogApi {LOG4J, SLF4J_LAL, SLF4J}

    @SuppressWarnings("serial")
    private static class Log4jLog implements Log, Serializable {

        private static final String FQCN = Log4jLog.class.getName();

        private static final LoggerContext loggerContext =
                LogManager.getContext(Log4jLog.class.getClassLoader(), false);

        private final ExtendedLogger logger;

        public Log4jLog(String name) {
            this.logger = loggerContext.getLogger(name);
        }

        @Override
        public boolean isFatalEnabled() {
            return this.logger.isEnabled(Level.FATAL);
        }

        @Override
        public boolean isErrorEnabled() {
            return this.logger.isEnabled(Level.ERROR);
        }

        @Override
        public boolean isWarnEnabled() {
            return this.logger.isEnabled(Level.WARN);
        }

        @Override
        public boolean isInfoEnabled() {
            return this.logger.isEnabled(Level.INFO);
        }

        @Override
        public boolean isDebugEnabled() {
            return this.logger.isEnabled(Level.DEBUG);
        }

        @Override
        public boolean isTraceEnabled() {
            return this.logger.isEnabled(Level.TRACE);
        }

        @Override
        public void fatal(Object message) {
            log(Level.FATAL, message, null);
        }

        @Override
        public void fatal(Object message, Throwable exception) {
            log(Level.FATAL, message, exception);
        }

        @Override
        public void error(Object message) {
            log(Level.ERROR, message, null);
        }

        @Override
        public void error(Object message, Throwable exception) {
            log(Level.ERROR, message, exception);
        }

        @Override
        public void warn(Object message) {
            log(Level.WARN, message, null);
        }

        @Override
        public void warn(Object message, Throwable exception) {
            log(Level.WARN, message, exception);
        }

        @Override
        public void info(Object message) {
            log(Level.INFO, message, null);
        }

        @Override
        public void info(String format, Object... arguments) {
            this.logger.info(format, arguments);
        }

        @Override
        public void info(Object message, Throwable exception) {
            log(Level.INFO, message, exception);
        }

        @Override
        public void debug(Object message) {
            log(Level.DEBUG, message, null);
        }

        @Override
        public void debug(Object message, Throwable exception) {
            log(Level.DEBUG, message, exception);
        }

        @Override
        public void trace(Object message) {
            log(Level.TRACE, message, null);
        }

        @Override
        public void trace(Object message, Throwable exception) {
            log(Level.TRACE, message, exception);
        }

        private void log(Level level, Object message, Throwable exception) {
            if (message instanceof String) {
                // Explicitly pass a String argument, avoiding Log4j's argument expansion
                // for message objects in case of "{}" sequences (SPR-16226)
                if (exception != null) {
                    this.logger.logIfEnabled(FQCN, level, null, (String) message, exception);
                }
                else {
                    this.logger.logIfEnabled(FQCN, level, null, (String) message);
                }
            }
            else {
                this.logger.logIfEnabled(FQCN, level, null, message, exception);
            }
        }
    }

    @SuppressWarnings("serial")
    private static class Slf4jLog<T extends Logger> implements Log, Serializable {

        protected final String name;

        protected transient T logger;

        public Slf4jLog(T logger) {
            this.name = logger.getName();
            this.logger = logger;
        }

        @Override
        public boolean isFatalEnabled() {
            return isErrorEnabled();
        }

        @Override
        public boolean isErrorEnabled() {
            return this.logger.isErrorEnabled();
        }

        @Override
        public boolean isWarnEnabled() {
            return this.logger.isWarnEnabled();
        }

        @Override
        public boolean isInfoEnabled() {
            return this.logger.isInfoEnabled();
        }

        @Override
        public boolean isDebugEnabled() {
            return this.logger.isDebugEnabled();
        }

        @Override
        public boolean isTraceEnabled() {
            return this.logger.isTraceEnabled();
        }

        @Override
        public void fatal(Object message) {
            error(message);
        }

        @Override
        public void fatal(Object message, Throwable exception) {
            error(message, exception);
        }

        @Override
        public void error(Object message) {
            if (message instanceof String || this.logger.isErrorEnabled()) {
                this.logger.error(String.valueOf(message));
            }
        }

        @Override
        public void error(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isErrorEnabled()) {
                this.logger.error(String.valueOf(message), exception);
            }
        }

        @Override
        public void warn(Object message) {
            if (message instanceof String || this.logger.isWarnEnabled()) {
                this.logger.warn(String.valueOf(message));
            }
        }

        @Override
        public void warn(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isWarnEnabled()) {
                this.logger.warn(String.valueOf(message), exception);
            }
        }

        @Override
        public void info(Object message) {
            if (message instanceof String || this.logger.isInfoEnabled()) {
                this.logger.info(String.valueOf(message));
            }
        }

        @Override
        public void info(String format, Object... arguments) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info(format, arguments);
            }
        }

        @Override
        public void info(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isInfoEnabled()) {
                this.logger.info(String.valueOf(message), exception);
            }
        }

        @Override
        public void debug(Object message) {
            if (message instanceof String || this.logger.isDebugEnabled()) {
                this.logger.debug(String.valueOf(message));
            }
        }

        @Override
        public void debug(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isDebugEnabled()) {
                this.logger.debug(String.valueOf(message), exception);
            }
        }

        @Override
        public void trace(Object message) {
            if (message instanceof String || this.logger.isTraceEnabled()) {
                this.logger.trace(String.valueOf(message));
            }
        }

        @Override
        public void trace(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isTraceEnabled()) {
                this.logger.trace(String.valueOf(message), exception);
            }
        }
    }

    @SuppressWarnings("serial")
    private static class Slf4jLocationAwareLog extends Slf4jLog<LocationAwareLogger> implements Serializable {

        private static final String FQCN = Slf4jLocationAwareLog.class.getName();

        public Slf4jLocationAwareLog(LocationAwareLogger logger) {
            super(logger);
        }

        @Override
        public void fatal(Object message) {
            error(message);
        }

        @Override
        public void fatal(Object message, Throwable exception) {
            error(message, exception);
        }

        @Override
        public void error(Object message) {
            if (message instanceof String || this.logger.isErrorEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.ERROR_INT, String.valueOf(message), null, null);
            }
        }

        @Override
        public void error(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isErrorEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.ERROR_INT, String.valueOf(message), null, exception);
            }
        }

        @Override
        public void warn(Object message) {
            if (message instanceof String || this.logger.isWarnEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, null);
            }
        }

        @Override
        public void warn(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isWarnEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.WARN_INT, String.valueOf(message), null, exception);
            }
        }

        @Override
        public void info(Object message) {
            if (message instanceof String || this.logger.isInfoEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.INFO_INT, String.valueOf(message), null, null);
            }
        }

        @Override
        public void info(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isInfoEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.INFO_INT, String.valueOf(message), null, exception);
            }
        }

        @Override
        public void debug(Object message) {
            if (message instanceof String || this.logger.isDebugEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, null);
            }
        }

        @Override
        public void debug(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isDebugEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, String.valueOf(message), null, exception);
            }
        }

        @Override
        public void trace(Object message) {
            if (message instanceof String || this.logger.isTraceEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, null);
            }
        }

        @Override
        public void trace(Object message, Throwable exception) {
            if (message instanceof String || this.logger.isTraceEnabled()) {
                this.logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, String.valueOf(message), null, exception);
            }
        }
    }
}
